package mancala;

import java.awt.EventQueue;
import java.awt.Graphics;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

import mancala.model.Board;
import mancala.model.House;
import mancala.model.Pit;
import mancala.model.Player;
import mancala.model.Store;
import mancala.ui.HouseImpl;
import mancala.ui.PlayerImpl;
import mancala.ui.StoreImpl;

public class Mancala extends JPanel {

    private static final long serialVersionUID = 1L;
    public static final int DEFAULT_STORE_STONE_COUNT = 0;
    public static final int DEFAULT_HOUSE_STONE_COUNT = 4;
    private JFrame frame;
    private Board board;

    /**
     * Entry point for the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    Mancala window = new Mancala();
                    window.frame.setVisible(true);
                    window.frame.repaint();
                    window.startGame();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     * 
     * @throws MalformedURLException
     */
    public Mancala() throws MalformedURLException {
        initialize();
    }

    // /////////////////////////////////////////////////////
    // INITIALIZATION
    // /////////////////////////////////////////////////////

    /**
     * Initialize the contents of the frame.
     * 
     * @throws MalformedURLException
     */
    private void initialize() throws MalformedURLException {
        frame = new JFrame();
        frame.setTitle("Mancala!");
        frame.setResizable(false);
        setLayout(null);
        frame.setBounds(100, 100, 800, 500);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(this);

        // Setup the game
        board = new Board();

        // Create players
        List<Player> players = Arrays.asList(createPlayer(1), createPlayer(2));

        // and their pits
        ArrayList<Pit> pits = new ArrayList<Pit>();
        for (Player player : players) {
            for (int i = 1; i < 7; i++) {
                pits.add(new HouseImpl(this, player, i));
            }
            pits.add(new StoreImpl(player));
        }

        // Link pits together and add them to the board
        for (int i = 0; i < pits.size(); i++) {
            Pit pit = pits.get(i);
            if (i == pits.size() - 1) { // Link last pit to the first one to get circular structure
                pit.setNext(pits.get(0));
            } else {
                pit.setNext(pits.get(i + 1));
            }
            add(pit);
        }

        // Setup the board
        board.setPlayers(players);
        board.setPits(pits);
    }

    // /////////////////////////////////////////////////////
    // GAME LOGIC
    // /////////////////////////////////////////////////////

    public void startGame() {
        Player player = (Player) board.getPlayers().get(0);
        player.setActive(true);
        // player.setIcon(getImageIcon("stones.png"));
        refreshUI();
    }

    public void makeMove(House clickedHouse) {
        Pit pit = clickedHouse;
        int stones = pit.getStones();
        pit.setStones(0);
        while (stones > 0) {
            pit = pit.getNext();
            if (pit instanceof Store && !pit.getPlayer().isActive()) {
                continue;
            }

            pit.setStones(pit.getStones() + 1);
            stones--;
        }

        // Check for free turn
        boolean isStore = pit instanceof Store;
        boolean isActive = pit.getPlayer().isActive();
        if (isStore && isActive) {
            refreshUI();
            // Are we there yet? :)
            checkEndConditions();
            return; // This doesn't toggle the player
        }

        // Grab opposite stones
        if (isActive && pit.getStones() == 1) {
            int pitNr = 1;
            Pit seekPit = pit;
            while ((seekPit = seekPit.getNext()) instanceof House) {
                pitNr++;
            }
            Pit activeStore = seekPit;
            if (seekPit != null) {
                for (int i = 0; i < pitNr; i++) {
                    seekPit = seekPit.getNext();
                }
                if (seekPit.getStones() > 0) { // Rule applies only if the opposite house has stones
                    pit.setStones(0);
                    activeStore.setStones(activeStore.getStones() + seekPit.getStones() + 1); // Also add the stone that triggered this rule
                    seekPit.setStones(0);
                }
            }
        }

        // Toggle player
        toggleActivePlayer();
        refreshUI();

        // Are we there yet? :)
        checkEndConditions();
    }

    @SuppressWarnings("unchecked")
    private void toggleActivePlayer() {
        for (Player player : (List<Player>) board.getPlayers()) {
            player.setActive(!player.isActive());
        }
    }

    private void checkEndConditions() {
        int p1Stones = 0;
        int p2Stones = 0;
        Pit p1Store = null, p2Store = null;

        @SuppressWarnings("unchecked")
        List<Pit> pits = board.getPits();

        // How many stones does ech player have in their houses?
        for (Pit pit : pits) {
            if (pit instanceof Store) {
                if (pit.getPlayer().getId() == 1) {
                    p1Store = pit;
                } else {
                    p2Store = pit;
                }
                continue;
            }

            if (pit.getPlayer().getId() == 1) {
                p1Stones += pit.getStones();
            } else {
                p2Stones += pit.getStones();
            }
        }
        if (p1Stones > 0 && p2Stones > 0) {
            return;
        }

        if (p1Store == null || p2Store == null) {
            throw new RuntimeException("There must be one store for each player!");
        }

        // Put remaining stones to stores and empty all the pits
        int p1Result = p1Store.getStones() + p1Stones;
        int p2Result = p2Store.getStones() + p2Stones;
        p1Store.setStones(p1Result);
        p2Store.setStones(p2Result);
        for (Pit pit : pits) {
            if (pit instanceof Store) {
                continue;
            }
            pit.setStones(0);
        }

        refreshUI();
        // Display results
        if (p1Result == p2Result) {
            JOptionPane.showMessageDialog(frame, "Game ends with a tie! Both players have " + p1Result + " stones!", "It's a draw!", JOptionPane.INFORMATION_MESSAGE);
        } else {
            int id = p1Result > p2Result ? 0 : 1;
            int result = p1Result > p2Result ? p1Result : p2Result;
            JOptionPane.showMessageDialog(frame, "Congratulations! " + ((Player) board.getPlayers().get(id)).getName() + " won the game with " + result + " stones!",
                    "Congratulations!", JOptionPane.INFORMATION_MESSAGE);
        }


        // Start a new game or exit
        Object[] options = {"Start a new game",
        "Exit the game"};
        int action = JOptionPane.showOptionDialog(frame,
                "What do you want to do?",
                "And now what...",
                JOptionPane.YES_NO_OPTION,
                JOptionPane.QUESTION_MESSAGE,
                null,
                options,
                options[0]);

        if(action == 0) {
            reset();
        } else {
            frame.dispose();
        }
    }

    private void reset() {
        @SuppressWarnings("unchecked")
        List<Pit> pits = board.getPits();
        for (Pit pit : pits) {
            if (pit instanceof Store) {
                pit.setStones(DEFAULT_STORE_STONE_COUNT);
            } else {
                pit.setStones(DEFAULT_HOUSE_STONE_COUNT);
            }
        }

        refreshUI();
    }

    // /////////////////////////////////////////////////////
    // UI CONTROL
    // /////////////////////////////////////////////////////

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        ImageIcon imageicon = getImageIcon("mancala.png");

        // Draw background
        if (imageicon != null) {
            g.drawImage(imageicon.getImage(), 0, 0, getWidth(), getHeight(), this);
        }
    }

    private void refreshUI() {
        frame.repaint();
    }

    // /////////////////////////////////////////////////////
    // OBJECT CREATION
    // /////////////////////////////////////////////////////

    private Player createPlayer(int id) {
        String defaultName = "Player " + id;
        String name = (String) JOptionPane.showInputDialog(
                frame,
                "Plase enter a name for player " + id,
                "Player name",
                JOptionPane.PLAIN_MESSAGE,
                null,
                null,
                defaultName);

        if (name == null || name.isEmpty()) {
            name = defaultName;
        }

        Player player = new PlayerImpl(id, name);
        add(player);
        return player;
    }

    public static ImageIcon getImageIcon(String name) {
        URL resource = Mancala.class.getResource("/mancala/ui/gfx/" + name);
        if (resource != null) {
            return new ImageIcon(resource);
        }

        return null;

    }
}